#include <Adafruit_GFX.h>
#include <Adafruit_ST7735.h>
#include <ESP8266WiFi.h>
#include <ArduinoJson.h>
#include <ESP8266HTTPClient.h>
#include "FS.h"
#include "DHT.h"
//#include "pic.h"
#include "hz.h"
#include <NTPClient.h>
#include <WiFiUdp.h>
#include "Ticker.h" //定时器库

#define TFT_CS 16
#define TFT_RST 5
#define TFT_DC 4

//st7735   esp8266
//cs       d0
//rst      d1
//rs       d2
//sdi      d7
//clk      d5
//led      d4

//dht d6

#define DHTPin 12
#define DHTTYPE DHT11

#define KEY_FLASH 0

#define BUFFPIXEL 20

Adafruit_ST7735 tft = Adafruit_ST7735(TFT_CS, TFT_DC, TFT_RST);

const char *ssid = "";
const char *password = "";
const char *url = "http://t.weather.itboy.net/api/weather/city/101010100";

const unsigned long BAUD_RATE = 115200;
const unsigned long HTTP_TIMEOUT = 5000;
const size_t MAX_CONTENT_SIZE = 10000;

float temperature;
float humidity;

bool isLoading = true;
String pic = "/p";
String ip = "";
//2 正常显示 4 反面镜像
int type = 4;

//dth的定时任务 10秒
jsc::Ticker dhtRefresh(10000);
//天气的定时任务 1小时
jsc::Ticker guiRefresh(1000 * 60 * 60);
//使劲按
jsc::Ticker timeRefresh(1000 * 60);

//初始化温度/湿度传感器
DHT dht(DHTPin, DHTTYPE);

HTTPClient http;
WiFiClient wifiClient;
WiFiUDP ntpUDP;
NTPClient timeClient(ntpUDP, "ntp.ntsc.ac.cn", 60 * 60 * 8, 30 * 60 * 1000);

/**
   显示文字（汉字除外）
*/
void showtext(int16_t x, int16_t y, uint8_t s, uint16_t fg, uint16_t bg, String str)
{
  //设置文本显示坐标，默认以左上角为参考点，
  tft.setCursor(x, y);
  // 设置文本颜色为白色，文本背景黑色
  tft.setTextColor(fg);
  // tft.fillScreen(bg);
  //设置文本大小，文本大小的范围是1-7的整数
  tft.setTextSize(s);
  // 设置显示的文字，注意这里有个换行符 \n 产生的效果
  tft.print(str);
}

/**
   显示单个汉字
*/
void showHanzi(int32_t x, int32_t y, const char c[3], uint32_t color)
{
  for (int k = 0; k < 39; k++) // 根据字库的字数调节循环的次数
    if (hanzi[k].Index[0] == c[0] && hanzi[k].Index[1] == c[1] && hanzi[k].Index[2] == c[2])
    {
      tft.drawBitmap(x, y, hanzi[k].hz_Id, hanzi[k].hz_width, 16, color);
    }
}
/*
   整句汉字显示
*/
void showHanziS(int32_t x, int32_t y, const char str[], uint32_t color)
{ //显示整句汉字，字库比较简单，上下、左右输出是在函数内实现
  int x0 = x;
  for (int i = 0; i < strlen(str); i += 3)
  {
    showHanzi(x0, y, str + i, color);
    x0 += 17;
  }
}

/**
   连接wif
*/
void connectToWifi()
{
  WiFi.mode(WIFI_STA); //设置esp8266 工作模式
  Serial.begin(BAUD_RATE);
  delay(10);
  Serial.println();
  Serial.println();
  Serial.print("Connecting to ");
  // showtext(5, 5, 1, ST7735_WHITE, ST7735_BLACK, "Connecting to " + ssid.to);
  Serial.println(ssid);
  WiFi.begin(ssid, password); //连接wifi
  while (WiFi.status() != WL_CONNECTED)
  {
    //这个函数是wifi连接状态，返回wifi链接状态
    delay(100);
    Serial.print(".");
    // tft.print(".");
    loading();
  }

  // tft.fillScreen(ST7735_BLACK);

  Serial.println("");
  Serial.println("WiFi connected");
  // tft.setCursor(0, 0);
  // tft.println("WiFi connected");
  delay(1000);
  Serial.println("IP address: ");
  Serial.println(WiFi.localIP()); // WiFi.localIP()返回8266获得的ip地址

  ip = WiFi.localIP().toString();

  // tft.print("IP address: ");
  // tft.println(WiFi.localIP());

  //delay(3000);
  isLoading = false;
}

/**
   显示本机ip
*/
void showIP() {
  tft.fillScreen(ST7735_BLACK);
  showtext(2, 60, 1, ST7735_WHITE, ST7735_BLACK, "local ip:" + ip);

  delay(5000);
  drawGUI();
}

/**
   显示 DHT
*/
void showLocalDHT11()
{
  temperature = dht.readTemperature(); // Gets the values of the temperature
  humidity = dht.readHumidity();       // Gets the values of the humidity
  Serial.print("室内温度: ");
  Serial.println(temperature);

  Serial.print("室内湿度: ");
  Serial.println(humidity);

  tft.fillRect(2, 40 , 30, 8, ST7735_BLACK);
  tft.fillRect(97, 40 , 30, 8, ST7735_BLACK);

  showtext(2, 40, 1, ST7735_WHITE, ST7735_BLACK, String(temperature));
  showtext(97, 40, 1, ST7735_WHITE, ST7735_BLACK, String(humidity));

  showHanziS(0, 22, "室温", ST7735_WHITE);
  showHanziS(95, 22, "室湿", ST7735_WHITE);
}

/**
   解析api json
*/
void parseUserData(char *content)
{
  Serial.println("开始解析");
  DynamicJsonBuffer jsonBuffer;

  JsonObject &root = jsonBuffer.parseObject(content);

  if (!root.success())
  {
    Serial.println("解析失败!");
    return;
  }

  // Serial.println("Print parsed data :");
  String high = root["data"]["forecast"][0]["high"];
  String low = root["data"]["forecast"][0]["low"];
  char ymd = root["data"]["forecast"][0]["ymd"];
  const char *week = root["data"]["forecast"][0]["week"];
  String shidu = root["data"]["shidu"];
  String pm25 = root["data"]["pm25"];
  //char updateTime = root["cityInfo"]["updateTime"];

  high.replace("高温", "");
  high.replace("℃", "");
  high.replace(" ", "");
  low.replace("低温", "");
  low.replace("℃", "");
  low.replace(" ", "");

  Serial.print("shidu: ");
  Serial.println(shidu);

  Serial.print("pm25: ");
  Serial.println(pm25);

  showtext(35, tft.height() - 13, 1, ST7735_WHITE, ST7735_BLACK, high);

  showtext(85, tft.height() - 13, 1, ST7735_WHITE, ST7735_BLACK, low);

  tft.drawFastHLine(0, 20, tft.width(), ST7735_WHITE);
  tft.drawFastHLine(0, tft.height() - 20, tft.width(), ST7735_WHITE);

  showHanziS(0, 2, "北京市", ST7735_WHITE);
  showHanziS(78, 2, week, ST7735_WHITE);

  showHanziS(0, tft.height() - 18, "高温", ST7735_WHITE);
  showHanziS(96, tft.height() - 18, "低温", ST7735_WHITE);

  showtext(2, 40, 1, ST7735_WHITE, ST7735_BLACK, String(pm25));
  showtext(97, 40, 1, ST7735_WHITE, ST7735_BLACK, String(shidu));

  //showHanziS(0, 22, "PM.25", ST7735_WHITE);
  showtext(0, 27, 1.5, ST7735_WHITE, ST7735_BLACK, "PM2.5");
  showHanziS(95, 22, "湿度", ST7735_WHITE);
}

/**
   请求天气api
*/
void request()
{
  //设置超时
  http.setTimeout(HTTP_TIMEOUT);
  //设置请求url
  http.begin(wifiClient, url);
  //以下为设置一些头  其实没什么用 最重要是后端服务器支持
  http.setUserAgent("esp8266");             //用户代理版本
  http.setAuthorization("esp8266", "boge"); //用户校验信息

  int httpCode = http.GET();
  if (httpCode > 0)
  {
    Serial.printf("[HTTP] GET... code: %d\n", httpCode);
    //判断请求是否成功
    if (httpCode == HTTP_CODE_OK)
    {
      //读取响应内容
      String response = http.getString();
      char *p = (char *)response.c_str();
      Serial.println("Get the data from Internet!");
      Serial.println(response);

      // UserData userData;
      //       if (parseUserData(p, &userData)) {
      //         //打印响应内容
      //         printUserData(&userData);
      //       }
      //解析响应内容 并且输出到屏幕
      parseUserData(p);
    }
  }
  else
  {
    Serial.printf("[HTTP] GET... failed, error: %s\n", http.errorToString(httpCode).c_str());
  }
  http.end();
}

/**
   显示
*/
void drawGUI()
{
  tft.fillScreen(ST7735_BLACK);
  request();
  // flag = true;

  //showLocalDHT11();
  showTime();
}

// These read 16- and 32-bit types from the SD card file.
// BMP data is stored little-endian, Arduino is little-endian too.
// May need to reverse subscript order if porting elsewhere.

uint16_t read16(File f)
{
  uint16_t result;
  ((uint8_t *)&result)[0] = f.read(); // LSB
  ((uint8_t *)&result)[1] = f.read(); // MSB
  return result;
}

uint32_t read32(File f)
{
  uint32_t result;
  ((uint8_t *)&result)[0] = f.read(); // LSB
  ((uint8_t *)&result)[1] = f.read();
  ((uint8_t *)&result)[2] = f.read();
  ((uint8_t *)&result)[3] = f.read(); // MSB
  return result;
}

/**
   读取spiflash的 bmp图片
*/
void bmpDraw(String filename, uint8_t x, uint8_t y)
{

  File bmpFile;
  int bmpWidth, bmpHeight;            // W+H in pixels
  uint8_t bmpDepth;                   // Bit depth (currently must be 24)
  uint32_t bmpImageoffset;            // Start of image data in file
  uint32_t rowSize;                   // Not always = bmpWidth; may have padding
  uint8_t sdbuffer[3 * BUFFPIXEL];    // pixel buffer (R+G+B per pixel)
  uint8_t buffidx = sizeof(sdbuffer); // Current position in sdbuffer
  boolean goodBmp = false;            // Set to true on valid header parse
  boolean flip = true;                // BMP is stored bottom-to-top
  int w, h, row, col;
  uint8_t r, g, b;
  uint32_t pos = 0, startTime = millis();

  if ((x >= tft.width()) || (y >= tft.height()))
    return;

  // Serial.println();
  // Serial.print("Loading image '");
  // Serial.print(filename);
  // Serial.println('\'');

  // Open requested file on SD card
  if ((bmpFile = SPIFFS.open(filename, "r")) == NULL)
  {
    Serial.print(filename);
    Serial.println(" not found");
    return;
  }

  // Parse BMP header
  if (read16(bmpFile) == 0x4D42)
  { // BMP signature
    // Serial.print("File size: ");
    // Serial.println(read32(bmpFile));
    read32(bmpFile);
    (void)read32(bmpFile);            // Read & ignore creator bytes
    bmpImageoffset = read32(bmpFile); // Start of image data
    // Serial.print("Image Offset: ");
    // Serial.println(bmpImageoffset, DEC);
    //  Read DIB header
    // Serial.print("Header size: ");
    // Serial.println(read32(bmpFile));
    read32(bmpFile);
    bmpWidth = read32(bmpFile);
    bmpHeight = read32(bmpFile);
    if (read16(bmpFile) == 1)
    { // # planes -- must be '1'
      bmpDepth = read16(bmpFile); // bits per pixel
      // Serial.print("Bit Depth: ");
      // Serial.println(bmpDepth);
      if ((bmpDepth == 24) && (read32(bmpFile) == 0))
      { // 0 = uncompressed

        goodBmp = true; // Supported BMP format -- proceed!
        // Serial.print("Image size: ");
        // Serial.print(bmpWidth);
        // Serial.print('x');
        // Serial.println(bmpHeight);

        // BMP rows are padded (if needed) to 4-byte boundary
        rowSize = (bmpWidth * 3 + 3) & ~3;

        // If bmpHeight is negative, image is in top-down order.
        // This is not canon but has been observed in the wild.
        if (bmpHeight < 0)
        {
          bmpHeight = -bmpHeight;
          flip = false;
        }

        // Crop area to be loaded
        w = bmpWidth;
        h = bmpHeight;
        if ((x + w - 1) >= tft.width())
          w = tft.width() - x;
        if ((y + h - 1) >= tft.height())
          h = tft.height() - y;

        // Set TFT address window to clipped image bounds
        tft.setAddrWindow(x, y, x + w - 1, y + h - 1);

        for (row = 0; row < h; row++)
        { // For each scanline...

          // Seek to start of scan line.  It might seem labor-
          // intensive to be doing this on every line, but this
          // method covers a lot of gritty details like cropping
          // and scanline padding.  Also, the seek only takes
          // place if the file position actually needs to change
          // (avoids a lot of cluster math in SD library).
          if (flip) // Bitmap is stored bottom-to-top order (normal BMP)
            pos = bmpImageoffset + (bmpHeight - 1 - row) * rowSize;
          else // Bitmap is stored top-to-bottom
            pos = bmpImageoffset + row * rowSize;
          if (bmpFile.position() != pos)
          { // Need seek?
            bmpFile.seek(pos);
            buffidx = sizeof(sdbuffer); // Force buffer reload
          }

          for (col = 0; col < w; col++)
          { // For each pixel...
            // Time to read more pixel data?
            if (buffidx >= sizeof(sdbuffer))
            { // Indeed
              bmpFile.read(sdbuffer, sizeof(sdbuffer));
              buffidx = 0; // Set index to beginning
            }

            // Convert pixel from BMP to TFT format, push to display
            b = sdbuffer[buffidx++];
            g = sdbuffer[buffidx++];
            r = sdbuffer[buffidx++];
            tft.pushColor(tft.Color565(r, g, b));
          } // end pixel
        }     // end scanline
        // Serial.print("Loaded in ");
        // Serial.print(millis() - startTime);
        // Serial.println(" ms");
      } // end goodBmp
    }
  }

  bmpFile.close();
  if (!goodBmp)
    Serial.println("BMP format not recognized.");
}

/**
   flash 按键
*/
void flashKeyPress()
{
  //读出当前按键状态，0表示抬起断开，1为按下接通

  int k = digitalRead(KEY_FLASH);
  if (k == 1)
  {
  }
  else
  {
    Serial.println("flashKey按下");
    //showIP();

    if (type  == 4) {
      type = 1;
    } else {
      type = type + 1;
    }

    Serial.print("type:");
    Serial.println(type);
    tft.setRotation(type);
    drawGUI();
  }
}

/**
   显示时间
*/
void showTime() {
  timeClient.update();
  tft.fillRect(34, 60 , 90, 16, ST7735_BLACK);
  int hour = timeClient.getHours();
  int minutes = timeClient.getMinutes();

  String minutesStr = minutes < 10 ? "0" + String(minutes) : String(minutes);
  String timeNow = String(hour) + ":" + minutesStr;
  showtext(34, 60, 2, ST7735_WHITE, ST7735_BLACK, timeNow);
}

void setup()
{
  Serial.begin(115200);
  tft.initR(INITR_144GREENTAB);
  tft.fillScreen(ST7735_BLACK);

  SPIFFS.begin();

  //参数为：0, 1, 2, 3 分别代表 0°、90°、180°、270°
  // 0x39正常颜色 屏幕90度显示 0x40反色屏幕0度显示 0x36反色 屏幕90度显示
  //修改"D:\Program Files (x86)\arduino-nightly\libraries\Adafruit_ST7735\Adafruit_ST7735.cpp"
  // setRotation 方法 rotation = m % 4; // can't be higher than 3 注释掉
  //增加：
  /**
     case 4:
      writedata(MADCTL_MX);
      _width = ST7735_TFTHEIGHT_144;
      _height = ST7735_TFTWIDTH;
     break;
  */
  tft.setRotation(type);

  // x,y,width,color
  // tft.drawBitmap(10, 13, p2, 35, 38, ST7735_WHITE);
  // showtext(80, 105, 1, ST7735_WHITE, ST7735_BLACK, "12:00");

  connectToWifi();

  pinMode(DHTPin, INPUT);
  dht.begin();

  drawGUI();

  //一小时执行一次 请求 天气接口
  //t1.attach(3600, request);

  //本地dth11 5分钟一次
  //t1.attach(30, showLocalDHT11);

}

/**
   加载动画
*/
void loading() {
  for (int i = 1; i <= 2; i++) {
    for (int j = 1; j <= 9; j++)
    {
      bmpDraw(pic + j + ".bmp", 32, 32);
    }
  }
}

void loop()
{
  while (isLoading) {
    loading();
  }

  flashKeyPress();

  //  if (dhtRefresh.elapsedTicks() > 0) {
  //    dhtRefresh.restart();
  //    showLocalDHT11();
  //  }
  if (guiRefresh.elapsedTicks() > 0) {
    guiRefresh.restart();
    drawGUI();
  }

  if (timeRefresh.elapsedTicks() > 0) {
    timeRefresh.restart();
    showTime();
  }
}
